// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 


using System;
using System.Text; 
using System.Collections.Generic;

using Galaxium.Protocol.Xmpp.Library.Xml;
	
namespace Galaxium.Protocol.Xmpp.Library.Extensions
{
	public enum DataFieldType {
		Boolean, TextSingle, TextMulti, TextPrivate, Fixed,
		Hidden, JidSingle, JidMulti, ListSingle, ListMulti
	}
	
	public class DataField
	{
		public DataField (Element field)
		{
			if (field.Name != "field")
				throw new ArgumentException ();
			
			Type = GetTypeFromString (field ["type"]);
			Identifier = field ["var"];
			Label = field ["label"];
			Description = field.GetTextChild ("desc", null);
			IsRequired = field.FirstChild ("required", null) != null;
			
			var values = new List<string> ();
			foreach (var val in field.EachChild ("value"))
				values.Add (val.Text);
			var value = (values.Count == 0) ? null : values [0];
			
			switch (Type) {
				case DataFieldType.Boolean:
					_value = value != null && (value == "1" || value.ToLower () == "true");
					break;
					
				case DataFieldType.Fixed:
				case DataFieldType.Hidden:
				case DataFieldType.TextSingle:
				case DataFieldType.TextPrivate:
					_value = value;
					break;
				
				case DataFieldType.JidSingle:
					_value = (JabberID) value;
					break;
					
				case DataFieldType.JidMulti:
					var jlist = new List<JabberID> ();
					foreach (var val in values)
						jlist.Add (val);
					_value = jlist;
					break;
					
				case DataFieldType.ListMulti:
					FillOptions (field);			
					_value = values.ToArray ();
					break;
					
				case DataFieldType.ListSingle:
					FillOptions (field);
					_value = value;
					break;
					
				case DataFieldType.TextMulti:
					_value = String.Join ("\n", values.ToArray ());;
					break;
			}
			
			values.Sort (Disco.OctetCollationComparer.Instance);
			_verification = Identifier + "<" + String.Join ("<", values.ToArray ()) + "<";
		}
		
		public DataField (string name, DataFieldType type)
		{
			Type = type;
			Identifier = name;
		}
		
		private void FillOptions (Element field)
		{
			var options = new List<ListOption> ();
			
			foreach (var child in field.EachChild ("option")) {
				var option = new ListOption () {
					Label = child ["label"],
					Value = child.GetTextChild ("value", null)
				};
				options.Add (option);
			}
				
			_options = options;
		}
		
		private IEnumerable<ListOption> _options;		
		private object _value;
		private string _verification;
		
		public DataFieldType Type { get; private set; }
		
		public string Identifier { get; private set; }
		
		public string Label { get; set; }
		
		public string Description { get; set; }
		
		public bool IsRequired { get; set; }
		
		#region Getters
		
		public IEnumerable<ListOption> GetOptions ()
		{
			if (Type != DataFieldType.ListSingle && Type != DataFieldType.ListMulti)
				throw new InvalidOperationException ();
			
			return _options;
		}
		
		public bool GetBool ()
		{
			if (Type != DataFieldType.Boolean)
				throw new InvalidOperationException ();

			return (bool) _value;
		}
		
		public string GetString ()
		{
			if (!(Type == DataFieldType.Fixed || Type == DataFieldType.Hidden ||
			      Type == DataFieldType.ListSingle || Type == DataFieldType.TextMulti ||
			      Type == DataFieldType.TextPrivate || Type == DataFieldType.TextSingle))
				throw new InvalidOperationException ();
			
			return (string) _value;
		}
		
		public IEnumerable<string> GetStrings ()
		{
			if (Type != DataFieldType.ListMulti)
				throw new InvalidOperationException ();
			
			return (IEnumerable<string>) _value;
		}
		
		public JabberID GetJid ()
		{
			if (Type != DataFieldType.JidSingle)
				throw new InvalidOperationException ();
			
			return (JabberID) _value;
		}
		
		public IEnumerable<JabberID> GetJids ()
		{
			if (Type != DataFieldType.JidMulti)
				throw new InvalidOperationException ();
			
			return (IEnumerable<JabberID>) _value;
		}
		
		public string GetVerificationString ()
		{
			return _verification;
		}
		
		#endregion
		
		#region Setters
		
		public void SetOptions (IEnumerable<ListOption> options)
		{
			if (Type != DataFieldType.ListSingle && Type != DataFieldType.ListMulti)
				throw new InvalidOperationException ();
			
			_options = options;
		}
		
		public void SetBool (bool value)
		{
			if (Type != DataFieldType.Boolean)
				throw new InvalidOperationException ();
			
			_value = value;			
		}
		
		public void SetString (string value)
		{
			if (!(Type == DataFieldType.Fixed || Type == DataFieldType.Hidden ||
			      Type == DataFieldType.ListSingle || Type == DataFieldType.TextMulti ||
			      Type == DataFieldType.TextPrivate || Type == DataFieldType.TextSingle))
				throw new InvalidOperationException ();
			
			_value = value;
		}
		
		public void SetStrings (IEnumerable<string> values)
		{
			if (Type != DataFieldType.ListMulti)
				throw new InvalidOperationException ();
			
			_value = values;
		}
		
		public void SetJid (JabberID jid)
		{
			if (Type != DataFieldType.JidSingle)
				throw new InvalidOperationException ();
			
			_value = jid;
		}
		
		public void SetJids (IEnumerable<JabberID> jids)
		{
			if (Type != DataFieldType.JidMulti)
				throw new InvalidOperationException ();
			
			_value = jids;
		}
		
		#endregion
			
		public Element ToXml (bool submit)
		{
			var e = new Element ("field");
			e ["var"] = Identifier;
			if (!submit)
				e ["type"] = GetTypeString (Type);
			
			if (!submit) {
				e ["label"] = Label;
			
				if (!String.IsNullOrEmpty (Description))
					e.AppendChild (new Element ("desc").SetText (Description));
				if (IsRequired)
					e.AppendChild (new Element ("required"));
			}

			switch (Type) {
				case DataFieldType.Boolean:
					var val = (bool) _value;
					if (val || submit)
						e.AppendChild (new Element ("value").SetText (val.ToString ()));
					break;
					
				case DataFieldType.Fixed:
					if (submit) return null;
					if (_value != null)
						e.AppendChild (new Element ("value").SetText (_value as string));
					break;
					
				case DataFieldType.Hidden:
				case DataFieldType.TextPrivate:
				case DataFieldType.TextSingle:
				case DataFieldType.JidSingle:
					if (_value != null)
						e.AppendChild (new Element ("value").SetText (_value.ToString ()));
					break;
					
				case DataFieldType.ListSingle:
					if (!submit) {
						foreach (var option in _options) {
							var opt = new Element ("option");
							opt ["label"] = option.Label;
							opt.AppendChild (new Element ("value").SetText (option.Value));
							e.AppendChild (opt);
						}
					}
					if (_value != null)
						e.AppendChild (new Element ("value").SetText (_value as string));
					break;
					
				case DataFieldType.ListMulti:
					if (!submit) {
						foreach (var option in _options) {
							var opt = new Element ("option");
							opt ["label"] = option.Label;
							opt.AppendChild (new Element ("value").SetText (option.Value));
						}
					}
					foreach (var value in (IEnumerable<string>) _value)
						e.AppendChild (new Element ("value").SetText (value));
					break;
					
				case DataFieldType.TextMulti:
					if (_value != null)
						foreach (var line in (_value as string).Split ('\n'))
							e.AppendChild (new Element ("value").SetText (line));
					break;
					
				case DataFieldType.JidMulti:
					foreach (var jid in (IEnumerable<JabberID>) _value)
						e.AppendChild (new Element ("value").SetText (jid));
					break;
			}
			
			return e;
		}
		
		public static string GetTypeString (DataFieldType type)
		{
			switch (type) {
				case DataFieldType.Boolean:     return "boolean";
				case DataFieldType.Fixed:       return "fixed";
				case DataFieldType.Hidden:      return "hidden";
				case DataFieldType.JidMulti:    return "jid-multi";
				case DataFieldType.JidSingle:   return "jid-single";
				case DataFieldType.ListMulti:   return "list-multi";
				case DataFieldType.ListSingle:  return "list-single";
				case DataFieldType.TextMulti:   return "text-multi";
				case DataFieldType.TextPrivate: return "text-private";
				case DataFieldType.TextSingle:  return "text-single";
				default: throw new ArgumentException ();
			}
		}
		
		public static DataFieldType GetTypeFromString (string type)
		{
			switch (type) {
				case "boolean":      return DataFieldType.Boolean;
				case "fixed":        return DataFieldType.Fixed;
				case "hidden":       return DataFieldType.Hidden;
				case "jid-multi":    return DataFieldType.JidMulti;
				case "jid-single":   return DataFieldType.JidSingle;
				case "list-multi":   return DataFieldType.ListMulti;
				case "list-single":  return DataFieldType.ListSingle;
				case "text-multi":   return DataFieldType.TextMulti;
				case "text-private": return DataFieldType.TextPrivate;
				case "text-single":  return DataFieldType.TextSingle;
				default:             return DataFieldType.TextSingle;
			}
		}
	}
}
